package models;

import java.util.*;

import javax.persistence.*;

import com.sun.corba.se.spi.orbutil.fsm.Guard.Result;

import play.data.validation.Required;
import play.db.jpa.*;
import play.modules.search.Field;
import play.modules.search.Indexed;

import controllers.Application;
import controllers.Application.*;

/**
 * 
 * @author Mostafa Fahim
 * 
 * @version 1.0
 */
@Entity
@Indexed
public class Organization extends Model {

	@Required
	@Field
	public String name;
	@Required
	@Lob
	@Field
	public String description;
	@OneToMany(mappedBy = "organization", cascade = CascadeType.PERSIST)
	public List<EEntity> entities;
	@Required
	public int privacyStatus;
	/** 0 indicates PUBLIC, 1 indicates PRIVATE, 2 indicates SECRET */
	@OneToMany(mappedBy = "organization", cascade = CascadeType.PERSIST)
	public List<RoleInOrganization> rolesAndUsers;
	@ManyToMany(mappedBy = "followedOrgs")
	public List<User> followingUsers;
	@ManyToMany
	public List<User> requestToJoinUsers;

	public int numberOfView;
	@OneToMany(mappedBy = "organization", cascade = CascadeType.PERSIST)
	public List<Request> orgRequests;

	public boolean allowingTagCreationForDevelopers = false;

	@ManyToMany(mappedBy = "blockedOrganizations")
	public List<User> blockedusers;

	@OneToMany(mappedBy = "featuredIn")
	public List<Idea> organizationFeaturedIdeas;

	public int howMuchPopular;

	/**
	 * 
	 * @param name
	 *            The name of the newly created organization
	 * @param description
	 *            The description of the aim behind the organization creation
	 * @param privacyStatus
	 *            Number indicating the privacy of the organization
	 */
	public Organization(String name, String description, int privacyStatus) {
		this.name = name;
		this.description = description;
		this.privacyStatus = privacyStatus;
		this.entities = new ArrayList<EEntity>();
		this.rolesAndUsers = new ArrayList<RoleInOrganization>();
		this.requestToJoinUsers = new ArrayList<User>();
		this.followingUsers = new ArrayList<User>();
		this.blockedusers = new ArrayList<User>();
		this.numberOfView = 0;
        this.orgRequests = new ArrayList<Request>();
		howMuchPopular = 0;
		organizationFeaturedIdeas = new ArrayList<Idea>();
	}

	/**
	 * Given a user and a private organization this method sends a request to
	 * join for the organization lead of the given organization
	 * 
	 * @author Shady
	 * @param userID
	 *            The Id of the user requesting to join
	 * @param organizationID
	 *            The organization ID that the request will be sent to
	 * 
	 * @return true if the request was sent successfully else returns false
	 */
	public static boolean requestJoin(long userID, long organizationID) {
		User requestingUser;
		Organization organization;

		requestingUser = User.findById(userID);
		organization = Organization.findById(organizationID);

		if (!organization.isPrivate()) {
			return false;
		}

		// check for duplicate requests
		if (organization.requestToJoinUsers.contains(requestingUser)) {
			return false;
		}

		// save a new request
		organization.requestToJoinUsers.add(requestingUser);

		// send notification (email) to the organization lead
		User organizationLead;
		organizationLead = organization.getOrganizationLead();

		if (organizationLead == null) {
			return false;
		}

		String message = "The user " + "\"" + requestingUser.name + "\""
				+ " with username " + "\"" + requestingUser.userName + "\""
				+ " requested to join the organization " + "\""
				+ organization.name + "\"";

		String subject = "Request to join " + organization.name;

		Application.sendMail(message, subject, organizationLead);

		return true;

	}

	/**
	 * This method is responsible for approving requests to join by the
	 * organization lead.
	 * 
	 * @author Shady
	 * 
	 * @param organizationLeadID
	 *            The id of the org. lead who will approve requests
	 * @param organizationID
	 *            The id of the organization the request was sent to
	 * @param requestingUserID
	 *            The id of the user who sent the request to join
	 * 
	 * @return true if it successfuly approved the user, else it fails.
	 */
	public static boolean approveJoin(long organizationLeadID,
			long organizationID, long requestingUserID) {
		User organizationLead;
		User requestingUser;
		Organization organization;

		organizationLead = User.findById(organizationLeadID);
		requestingUser = User.findById(requestingUserID);
		organization = Organization.findById(organizationID);

		if (!organization.isPrivate()) {
			return false;
		}

		// verify that the organizationLead ID given is really an
		// organizationLead of the organization
		if (organization.getOrganizationLead() == null
				|| organization.getOrganizationLead().id != organizationLeadID) {
			return false;
		}

		// remove the user request to join
		organization.requestToJoinUsers.remove(requestingUser);

		// add the user to the organization as an IdeaDeveloper

		Role role = new Role("IdeaDeveloper").save();
		RoleInOrganization roleInOrg = new RoleInOrganization(organization,
				requestingUser, role).save();

		// send a notification for the user

		String message = "you have been approved in the organization " + "\""
				+ organization.name + "\"";

		String subject = "Request approved.";

		Application.sendMail(message, subject, requestingUser);

		return true;

	}

	/**
	 * it's meant to add a user and a role for this user encapsulated in
	 * RoleInOrganization instance.
	 * 
	 * @param roleAndUser
	 *            a RoleInOrganization instance that contains a user and his
	 *            role in this organization
	 * @return this instance of Organization.
	 */
	public Organization addRoleAndUser(RoleInOrganization roleAndUser) {
		this.rolesAndUsers.add(roleAndUser);
		this.save();
		return this;
	}

	/**
	 * This method returns an Organization by a given Organization name
	 * 
	 * @param orgName
	 * @return Organization
	 */
	public static Organization getOrganizationByName(String orgName) {
		return Organization.find("byName", orgName).first();
	}

	public List<EEntity> getEntities() {
		return entities;
	}

	/**
	 * 
	 * This method is used to check if the organization is private or not
	 * 
	 * @author Hesham AL-Razaz This method is used to check if the organization
	 *         is private or not This method is used to check if the
	 *         organization is private or not
	 * 
	 * 
	 * @return True if privacyStatus is equal to 1 which means its private,
	 *         False otherwise
	 * */
	public boolean isPrivate() {
		if (privacyStatus == 1)
			return true;
		else
			return false;

	}

	/**
	 * This method checks whether the organization is secret and returns a
	 * boolean variable specifying so.
	 * 
	 * @author Mostafa Ali
	 * 
	 * @return True if privacyStatus is equal to 2, otherwise it returns false.
	 * 
	 * */
	public boolean isSecret() {
		return (this.privacyStatus == 2);
	}

	public void changeStructure(String orgName, String orgDescription,
			int privacyStatus) {
		this.name = orgName;
		this.description = orgDescription;
		this.privacyStatus = privacyStatus;
	}

	public void addEntityToOrganization(String entityName,
			String entityDescription) {
		EEntity newEntity = new EEntity(entityName, entityDescription, this);
		entities.add(newEntity);
		save();
		newEntity.save();

		/*
		 * for(User user : toBeNotified){
		 * user.notifications.add(newNotification); user.save(); }
		 */

	}

	/**
	 * A method that checks whether a given user is member of this organization
	 * 
	 * @param userID
	 *            The ID of the user to be checked on
	 * 
	 * @return A boolean value, true if the user is member of the organization,
	 *         false, if he isn't
	 * 
	 * @author Johannes Schmidt
	 * 
	 */
	public boolean isMember(long userID) {
		if (userID == -1)
			return false;
		// Get the user object
		User user = User.find("byID", userID).first();
		// If the role of the user is not null, he is member of the organization
		return RoleInOrganization.findRole(this, user) != null;
	}

	/**
	 * A method that returns all the organizers in a certain organization
	 * 
	 * @author Omar Darwish
	 * @return A list of all organizers of the organization instance the method
	 *         called on.
	 */
	public LinkedList<User> getOrganizersList() {
		LinkedList<User> result = new LinkedList<User>();
		List<RoleInEntity> list = RoleInEntity.findAll();
		for (EEntity entity : entities) {
			for (RoleInEntity item : list) {
				if (item.entity.id == entity.id
						&& item.role.description.equalsIgnoreCase("Organizer")
						&& !(result.contains(item.user))) {
					result.add(item.user);
				}
			}
		}
		return result;
	}

	/**
	 * A method that returns the organization lead of this organization
	 * 
	 * @return A the organization lead of this organization
	 * 
	 * @author Johannes Schmidt
	 * 
	 */
	public User getOrganizationLead() {
		for (int i = 0; i < this.rolesAndUsers.size(); i++) {
			if (this.rolesAndUsers.get(i).role.description
					.equalsIgnoreCase("OrganizationLead")) {
				return this.rolesAndUsers.get(i).user;
			}
		}
		// In case no organization lead exists within the organization, null
		// will be returned
		return null;
	}

	/**
	 * A method that filters all organizations for organizations matching a
	 * specific keyword
	 * 
	 * @param String
	 *            keyword The keyword the title/description of the organization
	 *            is matched with
	 * 
	 * @return A list of organizations, matching the keyword
	 * 
	 */
	public static List<Organization> getOrganizationsByKeyword(String keyword) {
		// This method takes a keyword as input and returns a list of
		// organizations with names/descriptions matching that keyword

		// add all organizations to a list
		List<Organization> allOrganizations = Organization.findAll();

		// create a list to store all the names in
		ArrayList<String> allTitles = new ArrayList<String>(
				allOrganizations.size());
		// create a list to store the desctiptions in
		ArrayList<String> allDescriptions = new ArrayList<String>(
				allOrganizations.size());
		// result list
		List<Organization> organizationsMatchingKeyword = new ArrayList<Organization>();

		for (int i = 0; i < allOrganizations.size(); i++) {
			// populate the name and description lists
			allTitles.add(allOrganizations.get(i).name);
			if (allOrganizations.get(i).description != null)
				allDescriptions.add(allOrganizations.get(i).description
						.toLowerCase());
			else
				allDescriptions.add("");
		}

		// <<<<<<< .mine
		// // for (int i = 0; i < allEntities.size(); i++) {
		// // // put all entities matching the keyword in our result list
		// // if (allTitles.get(i).equalsIgnoreCase(keyword)||
		// allDescriptions.get(i).contains(keyword.toLowerCase())) {
		// // organizationsMatchingKeyword.add(allEntities.get(i));
		// // }
		// // }
		// =======
		for (int i = 0; i < allOrganizations.size(); i++) {
			System.out.println(allOrganizations.get(i).name);
			// put all organizations matching the keyword in our result list
			if (allTitles.get(i).equalsIgnoreCase(keyword)
					|| allDescriptions.get(i).contains(keyword.toLowerCase())) {
				organizationsMatchingKeyword.add(allOrganizations.get(i));
			}
		}
		// >>>>>>> .r254
		// return a list with all the matching organizations
		return organizationsMatchingKeyword;
	}

	/**
	 * A method returning a list of this organization's users
	 * 
	 * @return A list of this organization's users
	 * 
	 * @author Johannes Schmidt
	 * 
	 */
	public List<User> getUsers() {
		// Create the result list
		List<User> allUsers = new ArrayList<User>(this.rolesAndUsers.size());

		for (int i = 0; i < rolesAndUsers.size(); i++) {
			// populate the users result list
			allUsers.add(rolesAndUsers.get(i).user);
		}
		return allUsers;
	}

	// public List<Organization> getOrganizationsOfOrganizationLead(long
	// userId){
	// List<Organization> result = new ArrayList<Organization>();
	//
	// List<RoleInOrganization> list = RoleInOrganization.findAll();
	// List<Organization> organization = Organization.findAll();
	//
	// for (RoleInOrganization record : list) {
	// for(Organization record2 : organization){
	// if (record.organization.id == record2.id
	// && record.user.id == userId &&
	// record.role.description.equalsIgnoreCase("Organization Lead") ) {
	// result.add(record2);
	// }
	// }
	// }
	// return result;
	// }

	public void editOrganization(String name, String description,
			int privacyStatus) {
		this.name = name;
		this.description = description;
		this.privacyStatus = privacyStatus;

	}

	/**
	 * @author AkrAmAtion
	 * 
	 * @return a String that will be used to export search results displaying an
	 *         organization whose name matches a keyword. The String is
	 *         formatted as Comma-Separated-Values
	 * */
	public String toString() {
		return "ORGANIZATION," + name + "," + description;
	}

	/**
	 * A method given a user ID check whether he is given lead privilages in
	 * this organization or not.
	 * 
	 * @author Omar Darwish
	 * @param userID
	 *            the id of the user
	 * @return true if he is given lead privilages and false otherwise
	 */
	public boolean isAsOrganizationLead(long userID) {
		for (RoleInOrganization roles : rolesAndUsers) {
			if (roles.user.id == userID
					&& roles.role.description
							.equalsIgnoreCase("AsOrganizationLead"))
				return true;
		}
		return false;
	}

	/**
	 * Gives a String of entities (space separated) that a specific user is
	 * their organizer in this organization.
	 * 
	 * @author Omar Darwish
	 * @param userID
	 *            The id of the user.
	 * @return A String of entities that the user with the previous id is
	 *         organizing in this organization.
	 */
	public String getOrganizingEntities(long userID) {
		String organizingEntities = "";
		User user = User.findById(userID);
		for (RoleInEntity roles : user.rolesAndEntities) {
			
			if (roles.entity.organization.id == this.id
					&& roles.role.description.equalsIgnoreCase("Organizer"))
				organizingEntities += roles.entity.name + " ";
		}
		return organizingEntities.trim();
	}

	/**
	 * A method that gets the entities of an organizer in an organization.
	 * 
	 * @author Alaa Tarek
	 * @param userID
	 *            this is the user id which is used to get his/her entities
	 * @return returns a list of entities of a specific user in this
	 *         organization
	 **/
	public List<EEntity> getEntitiesOfOrganizer(long userID) {
		List<EEntity> myEntities = new ArrayList<EEntity>();
		List<EEntity> entities2 =this.entities;
		User user = User.findById(userID);
		for (int i = 0; i < user.rolesAndEntities.size(); i++) {
			for (int j = 0; j < entities2.size(); j++) {
				if (user.rolesAndEntities.get(i).entity.id
						==entities2.get(j).id) {
					myEntities.add(user.rolesAndEntities.get(i).entity);
					System.out.println(user.rolesAndEntities.get(i).entity.name+"hereee");
					entities2.remove(j);
				}
			}
		}
		return myEntities;

	}

	/**
	 * gives back a list of all users of this organization who are given lead
	 * privileges
	 * 
	 * @author Omar Darwish
	 * @return returns a linkedlist of all users who are given all lead
	 *         privileges
	 * @story C1-S3ReverseAction
	 */
	public LinkedList<User> getAsOrganizationLeadList() {
		LinkedList<User> result = new LinkedList<User>();
		for (RoleInOrganization item : rolesAndUsers) {
			if (item.role.description.equalsIgnoreCase("AsOrganizationLead")
					&& !result.contains(item.user))
				result.add(item.user);
		}
		return result;
	}

	/**
	 * a method that gets the organization lead of the organization
	 * 
	 * @author Alaa Tarek
	 * @return the organization lead
	 */
	public Long organizationLead() {
		for (int i = 0; i < rolesAndUsers.size(); i++) {
			if (rolesAndUsers.get(i).role.description
					.equalsIgnoreCase("OrganizationLead")) {
				return rolesAndUsers.get(i).user.id;
			}

		}
		return (long) 0;

	}

	/**
	 * @author Shady
	 * 
	 * @return A list of all the Idea developers in the organization
	 * 
	 */
	public List<User> getIdeaDevelopers() {
		List<User> allIdeaDevelopers = new ArrayList<User>();
		List<EEntity> entitiesOfOrganization = EEntity.find("byOrganization",
				this).fetch();

		for (RoleInOrganization roles : rolesAndUsers) {
			if (roles.role.description.equalsIgnoreCase("IdeaDeveloper")) {
				if (!allIdeaDevelopers.contains(roles.user)) {
					allIdeaDevelopers.add(roles.user);
				}
			}
		}

		for (EEntity entity : entitiesOfOrganization) {
			for (RoleInEntity roles : entity.rolesAndUsers) {
				if (roles.role.description.equalsIgnoreCase("IdeaDeveloper")) {
					if (!allIdeaDevelopers.contains(roles.user)) {
						allIdeaDevelopers.add(roles.user);
					}
				}
			}
		}

		return allIdeaDevelopers;
	}

	/**
	 * A method to check if a user is an Idea Developer or not
	 * 
	 * @author Shady
	 * @param userID
	 *            the id of the user
	 * @return true if he is an Idea Developer in the organization otherwise
	 *         returns false
	 */
	public boolean isIdeaDeveloper(long userID) {
		User user = User.find("byID", userID).first();

		if (user == null) {
			return false;
		}

		if (getIdeaDevelopers() == null) {
			return false;
		}

		if (getIdeaDevelopers().contains(user)) {
			return true;
		} else {
			return false;
		}

	}

	/**
	 * A method that allows an Idea Developer to remove himself 
	 * from an organization
	 * 
	 * @author Shady
	 * @param ideaDeveloperID
	 *            the id of the user who wishes to be removed
	 * @param organizationID
	 *                        the id of the organization from which he wants
	 *                        to be removed from               
	 * @return true if he is successfully removed else returns
	 * false
	 */
	public static boolean removeSelfFromOrganization(long ideaDeveloperID, long organizationID) {
		Organization organization = Organization.findById(organizationID);
		User userToBeRemoved = User.findById(ideaDeveloperID);
		List<EEntity> entitiesOfOrganization = EEntity.find("byOrganization", organization).fetch();
		
		//remove the user from every entity in the organization
		for(EEntity entity:entitiesOfOrganization){
			if(EEntity.isIdeaDeveloper(entity.id, ideaDeveloperID)) {
				entity.removeMember(ideaDeveloperID);
			}
		}
		
		//remove the user from the organization
		RoleInOrganization roleToBeRemoved = null;
		
		for (int i = 0; i < organization.rolesAndUsers.size(); i++) {
			if (organization.rolesAndUsers.get(i).user == userToBeRemoved) {
				roleToBeRemoved = organization.rolesAndUsers.get(i);
				if (roleToBeRemoved.role.description.equalsIgnoreCase("IdeaDeveloper")){
					organization.rolesAndUsers.remove(i);
					models.RoleInOrganization.delete("id", roleToBeRemoved.id);
				}
			}
		}
		
		
		if (organization.privacyStatus == 1 || organization.privacyStatus == 2) {
			//remove the user from the following list
			
			if (organization.followingUsers.contains(userToBeRemoved)) {
				organization.followingUsers.remove(userToBeRemoved);
			}
			
			if (userToBeRemoved.followedOrgs.contains(organization)) {
				userToBeRemoved.followedOrgs.remove(organization);
			}

		}
		
		
		organization.save();
		userToBeRemoved.save();
		
		return true;
	}
	
	/**
	 * A method that allows an organization lead to remove
	 * idea developers from his organization
	 * 
	 * @author Shady
	 * @param ideaDeveloperID
	 *            the id of the user who will be removed
	 * @param organizationID
	 *                        the id of the organization from which the user
	 *                        will be removed from
	 * @return true if he is successfully removed else returns
	 * false
	 */
	public static boolean removeMemberFromOrganization(long organizationLeadID, long ideaDeveloperID, long organizationID) {
		Organization organization = Organization.findById(organizationID);
		User userToBeRemoved = User.findById(ideaDeveloperID);
		User organizationLead = User.findById(organizationLeadID);
		List<EEntity> entitiesOfOrganization = EEntity.find("byOrganization", organization).fetch();
		
		

		if (organization.getOrganizationLead().equals(organizationLead) || organization.isAsOrganizationLead(organizationLeadID) || organizationLead.isSysteAdmin()) {

			//remove the user from every entity in the organization
			for(EEntity entity:entitiesOfOrganization){
				if(EEntity.isIdeaDeveloper(entity.id, ideaDeveloperID)) {
					entity.removeMember(ideaDeveloperID);
				}
			}
			
			//remove the user from the organization
			RoleInOrganization roleToBeRemoved = null;
			
			for (int i = 0; i < organization.rolesAndUsers.size(); i++) {
				if (organization.rolesAndUsers.get(i).user == userToBeRemoved) {
					roleToBeRemoved = organization.rolesAndUsers.get(i);
					if (roleToBeRemoved.role.description.equalsIgnoreCase("IdeaDeveloper")){
						organization.rolesAndUsers.remove(i);
						models.RoleInOrganization.delete("id", roleToBeRemoved.id);
					}
				}
			}
			
			
			if (organization.privacyStatus == 1 || organization.privacyStatus == 2) {
				//remove the user from the following list
				
				if (organization.followingUsers.contains(userToBeRemoved)) {
					organization.followingUsers.remove(userToBeRemoved);
				}
				
				if (userToBeRemoved.followedOrgs.contains(organization)) {
					userToBeRemoved.followedOrgs.remove(organization);
				}
				
			}
			
			organization.save();
			userToBeRemoved.save();
			
			return true;
                }
                
                
                return false;
        }

	/**
	 * @author Ahmed Mahmoud
	 * @return
	 */
	public boolean cascadeDelete() {
		while (!entities.isEmpty()) {
			EEntity entity = entities.get(0);
			entity.cascadeDelete();
		}
		while (!rolesAndUsers.isEmpty()) {
			RoleInOrganization roleInOrganization = rolesAndUsers.get(0);
			roleInOrganization.cascadeDelete();
		}
		for (int i = 0; i < followingUsers.size(); i++) {
			User user = followingUsers.get(i);
			user.followedOrgs.remove(this);
			user.save();
		}
		while (!orgRequests.isEmpty()) {
			Request request = orgRequests.get(0);
			request.cascadeDelete();
		}
		for (int i = 0; i < blockedusers.size(); i++) {
			User user = blockedusers.get(i);
			user.blockedOrganizations.remove(this);
			user.save();
		}
		this.delete();
		return true;
	}
	
	/**
	 * A method that gets a summarized string of the description of the organization
	 * to be used in certain views to give a brief overview about the organization
	 * 
	 * @author Mostafa Ali
	 * 
	 * @return string containing brief description(maximum 40 characters)
	 * 
	 * */

	public String getSummarizedDescription() {
		if (description.length() < 40)
			return description;
		else
			return description.substring(0, 40) + "...";
	}
	
}
